// =======================================================================
// GUIslice library (extensions)
// - Calvin Hass
// - https://www.impulseadventure.com/elec/guislice-gui.html
// - https://github.com/ImpulseAdventure/GUIslice
// =======================================================================
//
// The MIT License
//
// Copyright 2016-2019 Calvin Hass
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// =======================================================================
/// \file XGauge.c



// GUIslice library
#include "GUIslice.h"
#include "GUIslice_drv.h"

#include "elem/XGauge.h"

#include <stdio.h>

#include <math.h>   // For sin/cos in XGauge(RADIAL)

#if (GSLC_USE_PROGMEM)
    #include <avr/pgmspace.h>
#endif

// ----------------------------------------------------------------------------
// Error Messages
// ----------------------------------------------------------------------------

extern const char GSLC_PMEM ERRSTR_NULL[];
extern const char GSLC_PMEM ERRSTR_PXD_NULL[];


// ----------------------------------------------------------------------------
// Extended element definitions
// ----------------------------------------------------------------------------
//
// - This file extends the core GUIslice functionality with
//   additional widget types
//
// ----------------------------------------------------------------------------


// ============================================================================
// Extended Element: Gauge
// - Basic progress bar with support for positive/negative state
//   and vertical / horizontal orientation.
// ============================================================================

// Create a gauge element and add it to the GUI element list
// - Defines default styling for the element
// - Defines callback for redraw but does not track touch/click
gslc_tsElemRef* gslc_ElemXGaugeCreate(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage,
  gslc_tsXGauge* pXData,gslc_tsRect rElem,
  int16_t nMin,int16_t nMax,int16_t nVal,gslc_tsColor colGauge,bool bVert)
{
  if ((pGui == NULL) || (pXData == NULL)) {
    static const char GSLC_PMEM FUNCSTR[] = "ElemXGaugeCreate";
    GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
    return NULL;
  }
  gslc_tsElem     sElem;
  gslc_tsElemRef* pElemRef = NULL;
  sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPEX_GAUGE,rElem,NULL,0,GSLC_FONT_NONE);
  sElem.nFeatures        |= GSLC_ELEM_FEA_FRAME_EN;
  sElem.nFeatures        |= GSLC_ELEM_FEA_FILL_EN;
  sElem.nFeatures        &= ~GSLC_ELEM_FEA_CLICK_EN;  // Element is not "clickable"
  sElem.nFeatures        &= ~GSLC_ELEM_FEA_GLOW_EN;
  sElem.nGroup            = GSLC_GROUP_ID_NONE;
  pXData->nMin            = nMin;
  pXData->nMax            = nMax;
  pXData->nVal            = nVal;
  pXData->nStyle          = GSLCX_GAUGE_STYLE_PROG_BAR;  // Default to progress bar
  pXData->bVert           = bVert;
  pXData->bFlip           = false;
  pXData->colGauge        = colGauge;
  pXData->colTick         = GSLC_COL_GRAY;
  pXData->nTickCnt        = 8;
  pXData->nTickLen        = 5;
  pXData->nIndicLen       = 10;     // Dummy default to be overridden
  pXData->nIndicTip       = 3;      // Dummy default to be overridden
  pXData->bIndicFill      = false;
  sElem.pXData            = (void*)(pXData);
  sElem.pfuncXDraw        = &gslc_ElemXGaugeDraw;
  sElem.pfuncXTouch       = NULL;           // No need to track touches
  sElem.colElemFill       = GSLC_COL_BLACK;
  sElem.colElemFillGlow   = GSLC_COL_BLACK;
  sElem.colElemFrame      = GSLC_COL_GRAY;
  sElem.colElemFrameGlow  = GSLC_COL_GRAY;
  if (nPage != GSLC_PAGE_NONE) {
    pElemRef = gslc_ElemAdd(pGui,nPage,&sElem,GSLC_ELEMREF_DEFAULT);
    return pElemRef;
#if (GSLC_FEATURE_COMPOUND)
  } else {
    // Save as temporary element
    pGui->sElemTmp = sElem;
    pGui->sElemRefTmp.pElem = &(pGui->sElemTmp);
    pGui->sElemRefTmp.eElemFlags = GSLC_ELEMREF_DEFAULT | GSLC_ELEMREF_REDRAW_FULL;
    return &(pGui->sElemRefTmp);
#endif
  }
  return NULL;
}


void gslc_ElemXGaugeSetStyle(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_teXGaugeStyle nStyle)
{
  if (pElemRef == NULL) {
    static const char GSLC_PMEM FUNCSTR[] = "ElemXGaugeSetStyle";
    GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
    return;
  }
  gslc_tsElem*    pElem = gslc_GetElemFromRef(pGui,pElemRef);
  gslc_tsXGauge*  pGauge  = (gslc_tsXGauge*)(pElem->pXData);

  // Update the type element
  pGauge->nStyle = nStyle;

  // Just in case we were called at runtime, mark as needing redraw
  gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
}

void gslc_ElemXGaugeSetIndicator(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_tsColor colGauge,
    uint16_t nIndicLen,uint16_t nIndicTip,bool bIndicFill)
{
  if (pElemRef == NULL) {
    static const char GSLC_PMEM FUNCSTR[] = "ElemXGaugeSetIndicator";
    GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
    return;
  }
  gslc_tsElem*    pElem = gslc_GetElemFromRef(pGui,pElemRef);
  gslc_tsXGauge*  pGauge  = (gslc_tsXGauge*)(pElem->pXData);

  // Update the config
  pGauge->colGauge    = colGauge;
  pGauge->nIndicLen   = nIndicLen;
  pGauge->nIndicTip   = nIndicTip;
  pGauge->bIndicFill  = bIndicFill;

  // Just in case we were called at runtime, mark as needing redraw
  gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
}

void gslc_ElemXGaugeSetTicks(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_tsColor colTick,uint16_t nTickCnt,uint16_t nTickLen)
{
  if (pElemRef == NULL) {
    static const char GSLC_PMEM FUNCSTR[] = "ElemXGaugeSetTicks";
    GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
    return;
  }
  gslc_tsElem*    pElem = gslc_GetElemFromRef(pGui,pElemRef);
  gslc_tsXGauge*  pGauge  = (gslc_tsXGauge*)(pElem->pXData);

  // Update the config
  pGauge->colTick   = colTick;
  pGauge->nTickCnt  = nTickCnt;
  pGauge->nTickLen  = nTickLen;

  // Just in case we were called at runtime, mark as needing redraw
  gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
}

// Update the gauge control's current position
void gslc_ElemXGaugeUpdate(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,int16_t nVal)
{
  if (pElemRef == NULL) {
    static const char GSLC_PMEM FUNCSTR[] = "ElemXGaugeUpdate";
    GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
    return;
  }
  gslc_tsElem*    pElem = gslc_GetElemFromRef(pGui,pElemRef);
  gslc_tsXGauge*  pGauge  = (gslc_tsXGauge*)(pElem->pXData);

  // Update the data element
  int16_t nValOld = pGauge->nVal;
  pGauge->nVal = nVal;

  // Element needs redraw
  if (nVal != nValOld) {
    // We only need an incremental redraw
    // NOTE: If the user configures the indicator to be
    //       long enough that it overlaps some of the gauge indicators
    //       then a full redraw should be done instead.
    gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_INC);
  }

}

// Update the gauge's fill direction
// - Setting bFlip causes the gauge to be filled in the reverse direction
//   to the default
// - Default fill direction for horizontal gauges: left-to-right
// - Default fill direction for vertical gauges: bottom-to-top
void gslc_ElemXGaugeSetFlip(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bFlip)
{
  if (pElemRef == NULL) {
    static const char GSLC_PMEM FUNCSTR[] = "ElemXGaugeSetFlip";
    GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
    return;
  }

  // Fetch the element's extended data structure
  gslc_tsElem*    pElem = gslc_GetElemFromRef(pGui,pElemRef);
  gslc_tsXGauge*  pGauge  = (gslc_tsXGauge*)(pElem->pXData);
  if (pGauge == NULL) {
    GSLC_DEBUG2_PRINT("ERROR: gslc_ElemXGaugeSetFlip(%s) pXData is NULL\n","");
    return;
  }
  pGauge->bFlip = bFlip;

  // Mark for redraw
  gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);

}


// Redraw the gauge
// - Note that this redraw is for the entire element rect region
// - The Draw function parameters use void pointers to allow for
//   simpler callback function definition & scalability.
bool gslc_ElemXGaugeDraw(void* pvGui,void* pvElemRef,gslc_teRedrawType eRedraw)
{
  if ((pvGui == NULL) || (pvElemRef == NULL)) {
    static const char GSLC_PMEM FUNCSTR[] = "ElemXGaugeDraw";
    GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
    return false;
  }

  // Typecast the parameters to match the GUI and element types
  gslc_tsGui*       pGui      = (gslc_tsGui*)(pvGui);
  gslc_tsElemRef*   pElemRef  = (gslc_tsElemRef*)(pvElemRef);
  gslc_tsElem*      pElem = gslc_GetElemFromRef(pGui,pElemRef);

  // Fetch the element's extended data structure
  gslc_tsXGauge* pGauge;
  pGauge = (gslc_tsXGauge*)(pElem->pXData);
  if (pGauge == NULL) {
    GSLC_DEBUG2_PRINT("ERROR: ElemXGaugeDraw(%s) pXData is NULL\n","");
    return false;
  }

  switch (pGauge->nStyle) {
    case GSLCX_GAUGE_STYLE_PROG_BAR:
      gslc_ElemXGaugeDrawProgressBar(pGui,pElemRef,eRedraw);
      break;
    case GSLCX_GAUGE_STYLE_RADIAL:
#if (GSLC_FEATURE_XGAUGE_RADIAL)
      gslc_ElemXGaugeDrawRadial(pGui,pElemRef,eRedraw);
#endif
      break;
    case GSLCX_GAUGE_STYLE_RAMP:
#if (GSLC_FEATURE_XGAUGE_RAMP)
      gslc_ElemXGaugeDrawRamp(pGui,pElemRef,eRedraw);
#endif
      break;
    default:
      // ERROR
      break;
  }

  // Save as "last state" to support incremental erase/redraw
  pGauge->nValLast      = pGauge->nVal;
  pGauge->bValLastValid = true;

  // Clear the redraw flag
  gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_NONE);

  return true;
}


bool gslc_ElemXGaugeDrawProgressBar(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_teRedrawType eRedraw)
{
  gslc_tsElem*    pElem = gslc_GetElemFromRef(pGui,pElemRef);
  gslc_tsXGauge*  pGauge  = (gslc_tsXGauge*)(pElem->pXData);

  gslc_tsRect   rTmp;           // Temporary rect for drawing
  gslc_tsRect   rGauge;         // Filled portion of gauge
  gslc_tsRect   rEmpty;         // Empty portion of gauge
  uint16_t      nElemW,nElemH;
  int16_t       nElemX0,nElemY0,nElemX1,nElemY1;
  int16_t       nGaugeX0,nGaugeY0,nGaugeX1,nGaugeY1;

  nElemX0 = pElem->rElem.x;
  nElemY0 = pElem->rElem.y;
  nElemX1 = pElem->rElem.x + pElem->rElem.w - 1;
  nElemY1 = pElem->rElem.y + pElem->rElem.h - 1;
  nElemW  = pElem->rElem.w;
  nElemH  = pElem->rElem.h;

  bool    bVert = pGauge->bVert;
  bool    bFlip = pGauge->bFlip;
  int16_t nMax  = pGauge->nMax;
  int16_t nMin  = pGauge->nMin;
  int16_t nRng  = pGauge->nMax - pGauge->nMin;

  uint32_t nScl = 1;
  int16_t nGaugeMid = 0;
  int16_t nLen = 0;
  int16_t nTmp = 0;
  int32_t nTmpL = 0;

  if (nRng == 0) {
    GSLC_DEBUG2_PRINT("ERROR: ElemXGaugeDraw() Zero gauge range [%d,%d]\n",nMin,nMax);
    return false;
  }

  if (bVert) {
    nScl = nElemH*32768/nRng;
  } else {
    nScl = nElemW*32768/nRng;
  }

  // Calculate the control midpoint/zeropoint (for display purposes)
  nTmpL = -((int32_t)nMin * (int32_t)nScl / 32768);
  nGaugeMid = (int16_t)nTmpL;


  // Calculate the length of the bar
  // - Use long mult/divide to avoid need for floating point
  nTmpL = (int32_t)(pGauge->nVal) * (int32_t)(nScl) / 32768;
  nLen  = (int16_t)(nTmpL);

  // Define the gauge's fill rectangle region
  // depending on the orientation (bVert) and whether
  // the current position is negative or positive.
  if (nLen >= 0) {
    if (bVert) {
      nGaugeY0 = nElemY0 + nGaugeMid;
      nGaugeY1 = nElemY0 + nGaugeMid + nLen;
    } else {
      nGaugeX0 = nElemX0 + nGaugeMid;
      nGaugeX1 = nElemX0 + nGaugeMid + nLen;
    }
  } else {
    if (bVert) {
      nGaugeY0 = nElemY0 + nGaugeMid + nLen;
      nGaugeY1 = nElemY0 + nGaugeMid;
    } else {
      nGaugeX0 = nElemX0 + nGaugeMid + nLen;
      nGaugeX1 = nElemX0 + nGaugeMid;
    }
  }
  if (bVert) {
    nGaugeX0 = nElemX0;
    nGaugeX1 = nElemX1;
  } else {
    nGaugeY0 = nElemY0;
    nGaugeY1 = nElemY1;
  }


  // Clip the region
  nGaugeX0 = (nGaugeX0 < nElemX0)? nElemX0 : nGaugeX0;
  nGaugeY0 = (nGaugeY0 < nElemY0)? nElemY0 : nGaugeY0;
  nGaugeX1 = (nGaugeX1 > nElemX1)? nElemX1 : nGaugeX1;
  nGaugeY1 = (nGaugeY1 > nElemY1)? nElemY1 : nGaugeY1;

  // Support flipping of gauge directionality
  // - The bFlip flag reverses the fill direction
  // - Vertical gauges are flipped by default

  if (bVert && !bFlip) {
    nTmp      = nElemY0+(nElemY1-nGaugeY1);  // nTmp will be swapped into nGaugeY0
    nGaugeY1  = nElemY1-(nGaugeY0-nElemY0);
    nGaugeY0  = nTmp;
    nGaugeMid = nElemH-nGaugeMid-1;
  } else if (!bVert && bFlip) {
    nTmp      = nElemX0+(nElemX1-nGaugeX1);  // nTmp will be swapped into nGaugeX0
    nGaugeX1  = nElemX1-(nGaugeX0-nElemX0);
    nGaugeX0  = nTmp;
    nGaugeMid = nElemW-nGaugeMid-1;
  }

  #ifdef DBG_LOG
  //printf("Gauge: nMin=%4d nMax=%4d nRng=%d nVal=%4d fScl=%6.3f nGaugeMid=%4d RectX=%4d RectW=%4d\n",
  //  nMin,nMax,nRng,pGauge->nGaugeVal,fScl,nGaugeMid,rGauge.x,rGauge.w);
  #endif

  // Draw a frame around the gauge
  // - Only draw this during full redraw
  if (eRedraw == GSLC_REDRAW_FULL) {
    gslc_DrawFrameRect(pGui, pElem->rElem, pElem->colElemFrame);
  }

  // To avoid flicker, we only erase the portion of the gauge
  // that isn't "filled". Determine the gauge empty region and erase it
  // There are two empty regions (one in negative and one in positive)
  int16_t nEmptyPos;
  if (bVert) {
    // Empty Region #1 (negative)
    nEmptyPos = (nGaugeY0 > nElemY1) ? nElemY1 : nGaugeY0;
    rEmpty = (gslc_tsRect){nElemX0,nElemY0,nElemX1-nElemX0+1,nEmptyPos-nElemY0+1};
    rTmp = gslc_ExpandRect(rEmpty,-1,-1);
    gslc_DrawFillRect(pGui,rTmp,pElem->colElemFill);
    // Empty Region #2 (positive)
    nEmptyPos = (nGaugeY1 < nElemY0) ? nElemY0 : nGaugeY1;
    rEmpty = (gslc_tsRect){nElemX0,nEmptyPos,nElemX1-nElemX0+1,nElemY1-nEmptyPos+1};
    rTmp = gslc_ExpandRect(rEmpty,-1,-1);
    gslc_DrawFillRect(pGui,rTmp,pElem->colElemFill);
  } else {
    // Empty Region #1 (negative)
    nEmptyPos = (nGaugeX0 > nElemX1) ? nElemX1 : nGaugeX0;
    rEmpty = (gslc_tsRect){nElemX0,nElemY0,nEmptyPos-nElemX0+1,nElemY1-nElemY0+1};
    rTmp = gslc_ExpandRect(rEmpty,-1,-1);
    gslc_DrawFillRect(pGui, rTmp, pElem->colElemFill);
    // Empty Region #2 (positive)
    nEmptyPos = (nGaugeX1 < nElemX0) ? nElemX0 : nGaugeX1;
    rEmpty = (gslc_tsRect){nEmptyPos,nElemY0,nElemX1-nEmptyPos+1,nElemY1-nElemY0+1};
    rTmp = gslc_ExpandRect(rEmpty,-1,-1);
    gslc_DrawFillRect(pGui, rTmp, pElem->colElemFill);
  }

  // Draw the gauge fill region
  rGauge = (gslc_tsRect){nGaugeX0,nGaugeY0,nGaugeX1-nGaugeX0+1,nGaugeY1-nGaugeY0+1};
  rTmp = gslc_ExpandRect(rGauge,-1,-1);
  gslc_DrawFillRect(pGui,rTmp,pGauge->colGauge);


  // Draw the midpoint line
  if (bVert) {
    if (nElemY0 + nGaugeMid < nElemY1) {
      gslc_DrawLine(pGui, nElemX0, nElemY0 + nGaugeMid, nElemX1, nElemY0 + nGaugeMid, pElem->colElemFrame);
    }
  } else {
    if (nElemX0 + nGaugeMid < nElemX1) {
      gslc_DrawLine(pGui, nElemX0 + nGaugeMid, nElemY0, nElemX0 + nGaugeMid, nElemY1, pElem->colElemFrame);
    }
  }



  return true;
}

#if (GSLC_FEATURE_XGAUGE_RADIAL)
void gslc_ElemXGaugeDrawRadialHelp(gslc_tsGui* pGui,int16_t nX,int16_t nY,uint16_t nArrowLen,uint16_t nArrowSz,int16_t n64Ang,bool bFill,gslc_tsColor colFrame)
{
  int16_t   nTipX,nTipY;
  int16_t   nBaseX1,nBaseY1,nBaseX2,nBaseY2;
  int16_t   nTipBaseX,nTipBaseY;

  gslc_PolarToXY(nArrowLen,n64Ang,&nTipX,&nTipY);
  gslc_PolarToXY(nArrowLen-nArrowSz,n64Ang,&nTipBaseX,&nTipBaseY);
  gslc_PolarToXY(nArrowSz,n64Ang-90*64,&nBaseX1,&nBaseY1);
  gslc_PolarToXY(nArrowSz,n64Ang+90*64,&nBaseX2,&nBaseY2);

  if (!bFill) {
    // Framed
    gslc_DrawLine(pGui,nX+nBaseX1,nY+nBaseY1,nX+nBaseX1+nTipBaseX,nY+nBaseY1+nTipBaseY,colFrame);
    gslc_DrawLine(pGui,nX+nBaseX2,nY+nBaseY2,nX+nBaseX2+nTipBaseX,nY+nBaseY2+nTipBaseY,colFrame);
    gslc_DrawLine(pGui,nX+nBaseX1+nTipBaseX,nY+nBaseY1+nTipBaseY,nX+nTipX,nY+nTipY,colFrame);
    gslc_DrawLine(pGui,nX+nBaseX2+nTipBaseX,nY+nBaseY2+nTipBaseY,nX+nTipX,nY+nTipY,colFrame);
    gslc_DrawLine(pGui,nX+nBaseX1,nY+nBaseY1,nX+nBaseX2,nY+nBaseY2,colFrame);

  } else {
    // Filled
    gslc_tsPt asPt[4];

    // Main body of pointer
    asPt[0] = (gslc_tsPt){nX+nBaseX1,nY+nBaseY1};
    asPt[1] = (gslc_tsPt){nX+nBaseX1+nTipBaseX,nY+nBaseY1+nTipBaseY};
    asPt[2] = (gslc_tsPt){nX+nBaseX2+nTipBaseX,nY+nBaseY2+nTipBaseY};
    asPt[3] = (gslc_tsPt){nX+nBaseX2,nY+nBaseY2};
    gslc_DrawFillQuad(pGui,asPt,colFrame);

    // Tip of pointer
    asPt[0] = (gslc_tsPt){nX+nBaseX1+nTipBaseX,nY+nBaseY1+nTipBaseY};
    asPt[1] = (gslc_tsPt){nX+nTipX,nY+nTipY};
    asPt[2] = (gslc_tsPt){nX+nBaseX2+nTipBaseX,nY+nBaseY2+nTipBaseY};
    gslc_DrawFillTriangle(pGui,asPt[0].x,asPt[0].y,asPt[1].x,asPt[1].y,asPt[2].x,asPt[2].y,colFrame);

  }

}

bool gslc_ElemXGaugeDrawRadial(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_teRedrawType eRedraw)
{
  gslc_tsElem*    pElem = gslc_GetElemFromRef(pGui,pElemRef);
  gslc_tsXGauge*  pGauge  = (gslc_tsXGauge*)(pElem->pXData);

  uint16_t      nElemW,nElemH,nElemRad;
  int16_t       nElemX0,nElemY0,nElemX1,nElemY1;
  int16_t       nElemMidX,nElemMidY;
  nElemX0   = pElem->rElem.x;
  nElemY0   = pElem->rElem.y;
  nElemX1   = pElem->rElem.x + pElem->rElem.w - 1;
  nElemY1   = pElem->rElem.y + pElem->rElem.h - 1;
  nElemMidX = (nElemX0+nElemX1)/2;
  nElemMidY = (nElemY0+nElemY1)/2;
  nElemW    = pElem->rElem.w;
  nElemH    = pElem->rElem.h;
  nElemRad  = (nElemW>=nElemH)? nElemH/2 : nElemW/2;

  int16_t   nMax            = pGauge->nMax;
  int16_t   nMin            = pGauge->nMin;
  int16_t   nRng            = pGauge->nMax - pGauge->nMin;
  int16_t   nVal            = pGauge->nVal;
  int16_t   nValLast        = pGauge->nValLast;
  bool      bValLastValid   = pGauge->bValLastValid;
  uint16_t  nTickLen        = pGauge->nTickLen;
  uint16_t  nTickAng        = 360 / pGauge->nTickCnt;
  uint16_t  nArrowLen       = pGauge->nIndicLen;
  uint16_t  nArrowSize      = pGauge->nIndicTip;
  bool      bFill           = pGauge->bIndicFill;

  int16_t   n64Ang,n64AngLast;
  int16_t   nInd;


  if (nRng == 0) {
    GSLC_DEBUG2_PRINT("ERROR: ElemXRadialDraw() Zero range [%d,%d]\n",nMin,nMax);
    return false;
  }

  // Support reversing of direction
  // TODO: Clean up excess integer typecasting
  if (pGauge->bFlip) {
    n64Ang      = (int32_t)(nMax - nVal    )* 360*64 /nRng;
    n64AngLast  = (int32_t)(nMax - nValLast)* 360*64 /nRng;
  } else {
    n64Ang      = (int32_t)(nVal     - nMin)* 360*64 /nRng;
    n64AngLast  = (int32_t)(nValLast - nMin)* 360*64 /nRng;
  }

  // Clear old
  if (bValLastValid) {
    gslc_ElemXGaugeDrawRadialHelp(pGui,nElemMidX,nElemMidY,nArrowLen,nArrowSize,n64AngLast,bFill,pElem->colElemFill);
  }

  // Draw frame
  if (eRedraw == GSLC_REDRAW_FULL) {
    gslc_DrawFillCircle(pGui,nElemMidX,nElemMidY,nElemRad,pElem->colElemFill);  // Erase first
    gslc_DrawFrameCircle(pGui,nElemMidX,nElemMidY,nElemRad,pElem->colElemFrame);
    for (nInd=0;nInd<360;nInd+=nTickAng) {
      gslc_DrawLinePolar(pGui,nElemMidX,nElemMidY,nElemRad-nTickLen,nElemRad,nInd*64,pGauge->colTick);
    }
  }

  // Draw pointer
  gslc_ElemXGaugeDrawRadialHelp(pGui,nElemMidX,nElemMidY,nArrowLen,nArrowSize,n64Ang,bFill,pGauge->colGauge);

  return true;
}
#endif // GSLC_FEATURE_XGAUGE_RADIAL

#ifdef GSLC_FEATURE_XGAUGE_RAMP
bool gslc_ElemXGaugeDrawRamp(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_teRedrawType eRedraw)
{
  gslc_tsElem*    pElem = gslc_GetElemFromRef(pGui,pElemRef);
  gslc_tsXGauge*  pGauge  = (gslc_tsXGauge*)(pElem->pXData);

  uint16_t      nElemW,nElemH;
  int16_t       nElemX0,nElemY1;
  nElemX0   = pElem->rElem.x;
  nElemY1   = pElem->rElem.y + pElem->rElem.h - 1;
  nElemW    = pElem->rElem.w;
  nElemH    = pElem->rElem.h;

  int16_t   nMax            = pGauge->nMax;
  int16_t   nMin            = pGauge->nMin;
  int16_t   nRng            = pGauge->nMax - pGauge->nMin;
  int16_t   nVal            = pGauge->nVal;
  int16_t   nValLast        = pGauge->nValLast;
  bool      bValLastValid   = pGauge->bValLastValid;
  int16_t   nInd;

  if (nRng == 0) {
    GSLC_DEBUG2_PRINT("ERROR: gslc_ElemXGaugeDrawRamp() Zero range [%d,%d]\n",nMin,nMax);
    return false;
  }

  uint32_t  nSclFX;
  uint16_t  nHeight;
  int32_t   nHeightTmp;
  uint16_t  nHeightBot;
  uint16_t  nX;
  uint16_t  nColInd;

  // Calculate region to draw or clear
  bool      bModeErase;
  int16_t   nValStart;
  int16_t   nValEnd;
  if ((eRedraw == GSLC_REDRAW_INC) && (!bValLastValid)) {
    // - If the request was incremental (GSLC_REDRAW_INC) but
    //   the last value wasn't marked as valid (!bValLastValid)
    //   then we want to force a full redraw.
    // - We don't expect to enter here since bValLastValid
    //   should always be set after we perform our first
    //   redraw.
    eRedraw = GSLC_REDRAW_FULL;
  }
  if (eRedraw == GSLC_REDRAW_FULL) {
    // If we haven't drawn anything before, draw full range from zero
    bModeErase  = false;
    nValStart   = 0;
    nValEnd     = nVal;
  } else {
    if (nVal >= nValLast) {
      // As we are advancing the control, we just draw the new range
      bModeErase  = false;
      nValStart   = nValLast;
      nValEnd     = nVal;
    } else {
      // Since we are retracting the control, we erase the new range
      bModeErase  = true;
      nValStart   = nVal;
      nValEnd     = nValLast;
    }
  }

  // Calculate the scaled gauge position
  // - TODO: Also support reversing of direction
  int16_t   nPosXStart,nPosXEnd;
  nPosXStart  = (nValStart - nMin)*nElemW/nRng;
  nPosXEnd    = (nValEnd   - nMin)*nElemW/nRng;

  nSclFX = (uint32_t)nElemH*32767/(nElemW*nElemW);

  for (nX=nPosXStart;nX<nPosXEnd;nX++) {
    nInd = nElemW-nX;
    nHeightTmp = nSclFX * nInd*nInd /32767;
    nHeight = nElemH-nHeightTmp;
    if (nHeight >= 20) {
      nHeightBot = nHeight-20;
    } else {
      nHeightBot = 0;
    }
    gslc_tsColor  nCol;
    uint16_t      nSteps = 10;
    uint16_t      nGap = 3;

    if (nSteps == 0) {
      nColInd = nX*1000/nElemW;
      nCol = gslc_ColorBlend3(GSLC_COL_GREEN,GSLC_COL_YELLOW,GSLC_COL_RED,500,nColInd);
    } else {
      uint16_t  nBlockLen,nSegLen,nSegInd,nSegOffset,nSegStart;
      nBlockLen = (nElemW-(nSteps-1)*nGap)/nSteps;
      nSegLen = nBlockLen + nGap;
      nSegInd = nX/nSegLen;
      nSegOffset = nX % nSegLen;
      nSegStart = nSegInd * nSegLen;

      if (nSegOffset <= nBlockLen) {
        // Inside block
        nColInd = (uint32_t)nSegStart*1000/nElemW;
        nCol = gslc_ColorBlend3(GSLC_COL_GREEN,GSLC_COL_YELLOW,GSLC_COL_RED,500,nColInd);

      } else {
        // Inside gap
        // - No draw
        nCol = pElem->colElemFill;
      }

    }

    if (bModeErase) {
      nCol = pElem->colElemFill;
    }
    gslc_DrawLine(pGui,nElemX0+nX,nElemY1-nHeightBot,nElemX0+nX,nElemY1-nHeight,nCol);

  }

  return true;
}
#endif // GLSC_FEATURE_XGAUGE_RAMP


// ============================================================================
